#pragma .h #import  <appkit/View.h> 
#pragma .h #import "Glyph.h"
#import "GlyphView.h"
#import <appkit/Application.h>
#import <appkit/Window.h>
#import <appkit/NXCursor.h>
#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <c.h>
#import "/local/grd/CB/MenuManager.h"
#import <appkit/ScrollView.h>
#import <appkit/publicWraps.h>
#import <objc/List.h>

static id glyphViewList ;

@implementation GlyphView: View
{ id fGView, bGView, scrollView ;
  Glyph *rootGlyph, *targetGlyph, *superGlyph ;
  int fGGState, bGGState ;
  NXRect targetFrame ; // targetGlyph's frame in my coord sys
  NXRect clipRect ;    // clipping rectangle around targetGlyph
  NXRect visibleRect ; // visible portion of rootGlyph
  NXPoint mouseOffset ; // distance from mouse hit to target's origin
  BOOL rootIsTarget, isAWindow ;
}

+ glyphViewList ;
{ // return the list of glyph views
  if(glyphViewList == nil)
    glyphViewList = [[List alloc] init] ;
  return glyphViewList ;
}

-blowUp ;
// Provide visual feedback that the target glyph is
// dying, then kill it.
{ NXRect aRect ;
  float dx, dy ;
  id aWin ;
  int i , knt = 20 ;
  // shrink window to nothing
  aWin = [fGView window] ;
  [aWin orderFront: self] ; // may be offscreen
  [aWin getFrame: &aRect] ;
  dx = aRect.size.width / (float) knt ;
  dy = aRect.size.height / (float) knt ;
  for(i = 0 ; i < knt ; i++)
  { [aWin sizeWindow: aRect.size.width :aRect.size.height] ;
    NXPing() ;
    aRect.size.width -= dx ;
    aRect.size.height -= dy ;
  }
  [aWin orderOut: self] ;
  [targetGlyph free] ; // actually, doesn't know how to free yet!
  targetGlyph = nil ;
  return self ; 
}


-clipRect: (NXRect *) aRect toGlyph: (Glyph *) aGlyph ;
// set aRect to the clipping rectangle imposed by aGlyph.
// This involves calculating intersection rectangles 
// back to the rootGlyph.  Just calculates the clipping
// rectangle; does not actually set it.
{ if(aGlyph->flags.isRoot)
  { aRect->origin.x = aGlyph->frame.origin.x ;
    aRect->origin.y = aGlyph->frame.origin.y ;
    aRect->size.width = aGlyph->frame.size.width ;
    aRect->size.height = aGlyph->frame.size.height ;
    return self ;
  }
  else
  { NXRect bRect = {{0.0,0.0},{0.0,0.0}} ;
    [self clipRect: aRect toGlyph: aGlyph->ancestor] ;
    bRect.size.height = aGlyph->frame.size.height ;
    bRect.size.width = aGlyph->frame.size.width ;
    [aGlyph convertToRootGlyph: &bRect.origin] ;
    NXIntersectionRect(&bRect,aRect) ;
  } 
  return self ; 
}

-convertToScreen: (NXPoint *) aPnt ;
// convert aPnt from my coord sys to screen coord sys
{  [self convertPoint: aPnt toView: nil] ;
   [window convertBaseToScreen: aPnt] ;
   return self ;
}

  
-drawSelf:(const NXRect *)rects :(int)rectCount ;
{ // drawSelf: does the displaying when my window
  // is first opened, when its resized, when its
  // scrolled, or when its unminiaturized.
  // composite background 
  PScomposite(rects->origin.x,
         rects->origin.y,
         rects->size.width,
         rects->size.height,
         bGGState,
         rects->origin.x, 
         rects->origin.y,
         NX_COPY) ;
  // and, if necessary, the foreground
  if(targetGlyph && NXIntersectsRect(rects, &targetFrame))
  { NXRect unionRect ;
    float x,y ;
    unionRect = targetFrame ;
    NXUnionRect(rects,&unionRect) ;
    x = unionRect.origin.x  - targetFrame.origin.x ;
    y = unionRect.origin.y  - targetFrame.origin.y ;
    PScomposite(x,
         y,
         unionRect.size.width,
         unionRect.size.height,
         fGGState,
         unionRect.origin.x, 
         unionRect.origin.y,
         NX_SOVER) ;
  }
  return self ;
}

- handOff: (NXPoint *) aPnt ;
{ // Attempts to handOff: the current targetGlyph to another
  // glyphView, if aPnt (the current mouse point in screen coords)
  // is within that view.  Deletes the target if it fails.  In
  // either case, sets the targetGlyph to nil.
  NXRect visRect ;
  NXPoint thePnt ;
  int found, gWinNum, fGWinNum, targetWin, i, knt ;
  id theList, theView  ;
  NXConvertWinNumToGlobal([[fGView window] windowNum],&fGWinNum) ;
  [[fGView window] orderOut: self] ; // don't find the fG window
  PSfindwindow(aPnt->x,aPnt->y,NX_ABOVE,0,
        &thePnt.x,&thePnt.y,&gWinNum,&found) ;
  if(!found)
    return nil ;
  NXConvertGlobalToWinNum(gWinNum, &targetWin) ; 
  theList = [GlyphView glyphViewList] ;
  knt = [theList count] ;
  for(i = 0 ; i < knt ; i++)
  { if(( [[(theView = [theList objectAt: i]) window] windowNum]) == targetWin)
    { // we've got a candidate...
      NXPoint viewPnt ;
      viewPnt = *aPnt ;
      [[theView window] convertScreenToBase: &viewPnt] ;
      [theView convertPoint: &viewPnt fromView: nil] ;
      [[[theView superview] superview] getDocVisibleRect: &visRect] ;
      if(NXPointInRect(&viewPnt,&visRect)) // give it away...
      { viewPnt.x -= mouseOffset.x ;
        viewPnt.y -= mouseOffset.y ;
        [theView receive: targetGlyph at: &viewPnt] ;
        targetGlyph = nil ;
        return self ;
      }
      else
        [self blowUp] ;
      break ;
    }
  }
  return nil ;
}


- mouseDown: (NXEvent *) anEvent ;
{ Glyph *tg ;
  BOOL newTarget = NO ;
  [self lockFocus] ;
  [self convertPoint: &anEvent->location fromView: nil] ;
  tg = [rootGlyph hitTest: &anEvent->location] ;
  if(tg != targetGlyph) // new target
  {  [self newTarget: tg] ;
     newTarget = YES ;
  }
  if(tg == rootGlyph)
  { rootIsTarget = YES ; 
    return self ; 
  }
  rootIsTarget = NO ;
   // set the clipping rectangle
  PSgsave() ;
  superGlyph = tg->ancestor ;
  [self clipRect: &clipRect toGlyph: superGlyph] ;
  NXRectClip(&clipRect) ;
 // remove target glyph from TTree
  [targetGlyph unlink] ;
  // calculate new mouse offset
  mouseOffset.x = anEvent->location.x - targetFrame.origin.x ;
  mouseOffset.y = anEvent->location.y - targetFrame.origin.y ;
  isAWindow = false ; // initially, glyph must be in our view
  // finally, highlight the new target
  if(newTarget)
  { [self lockFocus] ;
    PSsetgray(0.666) ;
    PScompositerect(targetFrame.origin.x,targetFrame.origin.y,
       targetFrame.size.width,targetFrame.size.height, NX_PLUS) ; 
    [self unlockFocus] ;
   [window flushWindow] ;
  }
  return self ;
}

 
- mouseDragged: (NXEvent *) anEvent ;
{ Glyph *aGlyph ;
  if(rootIsTarget)
    return self ; // can't drag the root glyph
  [self convertPoint: &anEvent->location fromView: nil] ;
   // see if we've dragged out of our window
  if(!isAWindow) // i.e. if glyph is in our view...
  {  // erase our old image
     PScomposite(targetFrame.origin.x, targetFrame.origin.y,
     targetFrame.size.width, targetFrame.size.height,
     bGGState, targetFrame.origin.x, targetFrame.origin.y,NX_COPY) ;
    if(!NXPointInRect(&anEvent->location,&visibleRect)) // just dragged out of view...
    { NXPoint winPnt ; // turn into a "travelling window"
      winPnt.x = targetFrame.origin.x ;
      winPnt.y = targetFrame.origin.y ;
      [self convertToScreen: &winPnt] ;
      [[fGView window] moveTo: winPnt.x :winPnt.y] ;
      [[fGView window] orderFront: self] ;
      isAWindow = YES ;
    }
  }
  else // glyph dragged out of our view, so is a "travelling window"
  { if(NXPointInRect(&anEvent->location,&visibleRect)) // dragged back in?
    { [[fGView window] orderOut: self] ;
      isAWindow = NO ;
    }
    else // glyph was, and still is, a "travelling window"
   {  NXPoint winPnt ; // move the "travelling window"
      winPnt.x = targetFrame.origin.x ;
      winPnt.y = targetFrame.origin.y ;
      [self convertPoint: &winPnt toView: nil] ;
      [window convertBaseToScreen:&winPnt] ;
      [[fGView window] moveTo: winPnt.x :winPnt.y] ;
    }    
  }
  // update the target's location
  targetFrame.origin.x = anEvent->location.x - mouseOffset.x ;
  targetFrame.origin.y = anEvent->location.y - mouseOffset.y ;
  if(!isAWindow) // if not a "travelling window"....
  { // see if clip rectangle must change
    aGlyph = [rootGlyph hitTest: &anEvent->location] ;
    if(aGlyph && (aGlyph != superGlyph)) 
    { // must reset the clip rectangle
      superGlyph = aGlyph ;
      PSgrestore() ;
      PSgsave() ;
      [self clipRect: &clipRect toGlyph: superGlyph] ;
      NXRectClip(&clipRect) ;
    }
    // draw our new image
     PScomposite(0.0, 0.0,targetFrame.size.width,targetFrame.size.height,
      fGGState,targetFrame.origin.x, targetFrame.origin.y,NX_PLUS) ; 
    // finally, highlight the new target
    [self lockFocus] ;
    PSsetgray(0.666) ;
    PScompositerect(targetFrame.origin.x,targetFrame.origin.y,
       targetFrame.size.width,targetFrame.size.height, NX_PLUS) ; 
    [self unlockFocus] ;
    [window flushWindow] ;
  }
  return self ;
}

- mouseUp: (NXEvent *) anEvent ;
{ if(rootIsTarget)
    return self ;
  PSgrestore() ;
  [self unlockFocus] ;
  if(isAWindow)
  { [[self window] convertBaseToScreen: &anEvent->location] ;
    [self handOff: &anEvent->location] ; // hand it to another view, or the void!
    return self ;
  }
  [self convertPoint: &anEvent->location fromView: nil] ;
  // relink the target
  // Is the next hitTest necessary?
  superGlyph = [rootGlyph hitTest: &anEvent->location] ;
  [superGlyph convertFromRootGlyph: &anEvent->location] ;
  [targetGlyph moveTo: anEvent->location.x - mouseOffset.x
                     : anEvent->location.y - mouseOffset.y ] ;
  [superGlyph plant: targetGlyph] ;
  return self ;
}


- newTarget: (Glyph *) tg ;
{ // This is called whenever a new target is selected.
  // (A smarter version
  // would know if the fg was "growing" or "shrinking".)
  // To repair the background, first composite the fGrnd 
  // into the background...
  [bGView lockFocus] ;
  if(targetGlyph != nil) 
  { // composite visible portion of old target into bGrnd
    NXIntersectionRect(&clipRect,&targetFrame) ;
    PScomposite(0.0,0.0,
      targetFrame.size.width,targetFrame.size.height,fGGState,
      targetFrame.origin.x, targetFrame.origin.y, NX_PLUS) ;
    targetGlyph->flags.isTarget = FALSE ;
   // repair the area in offscreen window
   [self lockFocus] ;
   PScomposite(targetFrame.origin.x,targetFrame.origin.y,
      targetFrame.size.width,targetFrame.size.height,bGGState,
      targetFrame.origin.x, targetFrame.origin.y, NX_COPY) ;
   [self unlockFocus] ;
  }
  // reset the targetGlyph ivars
  targetGlyph = tg ;
  tg->flags.isTarget = TRUE ;
  if(tg == rootGlyph) // life is easy if rootGlyph selected!
  { [window flushWindow] ;
    return self ;
  }
  else 
  { // calculate the new target frame (location of
    // new target in my coord system)...
    targetFrame.origin.x = targetFrame.origin.y = 0.0 ;
    targetFrame.size.width = tg->frame.size.width ;
    targetFrame.size.height = tg->frame.size.height ;
    [tg convertToRootGlyph: &targetFrame.origin] ;
    // redraw background under targetFrame, without
    // redrawing the target Glyph itself...
    tg->flags.noDraw = TRUE ;
    [rootGlyph display: &targetFrame] ;
    tg->flags.noDraw = FALSE ;
    [bGView unlockFocus] ;
    // now draw the new target into the fGRnd window
    [fGView lockFocus] ;
    [[fGView window] sizeWindow: targetFrame.size.width
	       :targetFrame.size.height] ;
    PSsetgray(1.0) ; // "clear" the old target
    PScompositerect(0.0, 0.0,
       targetFrame.size.width,targetFrame.size.height, NX_COPY) ;
    PSsetgray(0.0) ;
    [tg display: &tg->frame] ;
    [fGView unlockFocus] ;
    return self ;
  }
}

- receive: (Glyph *) aGlyt at: (NXPoint *) aPnt  ;
{ // add the Glyt to my TTree, with aPnt its origin,
  // given in my coord sys
  Glyph * superG ;
  superG = [rootGlyph hitTest: aPnt] ;
  if(superG) // this should always be true
  { NXRect aRect ;
    aRect.origin = aGlyt->frame.origin = *aPnt ;
    aRect.size = aGlyt->frame.size ;
    [superG convertFromRootGlyph: &(aGlyt->frame.origin)] ;
    [superG plant: aGlyt] ;
    [bGView lockFocus] ;
    [rootGlyph display: &bounds] ; // aRect?
    [bGView unlockFocus] ;
    [self lockFocus] ;
    [self drawSelf: &aRect :1] ;
    [self unlockFocus] ;
    [window flushWindow] ;   
  }
  return self ;
}

- rightMouseDown: (NXEvent *) anEvent ;
{ [window addToEventMask: NX_LMOUSEDRAGGEDMASK] ;
  [window addToEventMask: NX_RMOUSEDRAGGEDMASK] ;
  [NXApp printf: "D"] ;
  return self ;
}


- setScrollView: anObject ;
{ // this method is invoked curing nib file opening.
  // The opportunity is used to replace the scrollview's
  // docview with ourselves, then do initial setup
  scrollView = anObject ;
  [scrollView setHorizScrollerRequired: YES] ;
  [[scrollView setDocView: self] free] ;
  [self setup] ;
  return self ;
}


- setup ;
{ // perform initial setup of a
  // brand new window
  id bGWindow, fGWindow ;
  NXRect aRect = {{600.0,0.0},{1.0,1.0}} ;
  [window disableCursorRects] ; 
  [window addToEventMask: NX_LMOUSEDRAGGEDMASK] ;
  [window addToEventMask: NX_RMOUSEDRAGGEDMASK] ;
  // create the background windows
  bGWindow = [Window new] ;
  [bGWindow initContent: &bounds style: NX_PLAINSTYLE
       backing: NX_RETAINED buttonMask:0 defer:NO] ;
  bGGState = [bGWindow gState] ; // MUST come AFTER initContent
  bGView = [bGWindow contentView] ;
  fGWindow = [Window new] ;
  [fGWindow initContent: &aRect style: NX_PLAINSTYLE
     backing: NX_RETAINED buttonMask:0 defer: NO] ;
  fGGState = [fGWindow gState] ;
  fGView = [fGWindow contentView] ;
  // setup the rootGlyph
  rootGlyph = [[Glyph alloc] init] ;
  rootGlyph->flags.isRoot = TRUE ;
  [rootGlyph sizeTo: frame.size.width :frame.size.height] ;
  [rootGlyph moveTo: 0.0 :0.0] ;
  // get current visible rectangle
  [[superview superview] getDocVisibleRect: &visibleRect] ;
  // add ourselves to list of glyph views
  [[GlyphView glyphViewList] addObject: self] ;
  // now cook them up for testing.. this will be
  // replaced by a display message to the rootglyph
  // with appropriate lockfocus
  [bGWindow orderFront: 0] ;
  [fGWindow orderFront: 0] ;
  { struct Glyph *aGlyph, *bGlyph, *cGlyph ;
    aGlyph = [[Glyph new] moveTo: 10.0 :15.0] ;
    bGlyph = [[Glyph new] moveTo: 25.0 :25.0] ;
    cGlyph = [[Glyph new] moveTo: 55.0 :55.0] ;
    [rootGlyph plant: aGlyph] ;
    [rootGlyph plant: bGlyph] ;
    [rootGlyph plant: cGlyph] ;
    [rootGlyph iam: "root"] ;
    [aGlyph iam: "a"] ;
    [bGlyph iam: "b"] ;
    [cGlyph iam: "c"] ;
    [bGView lockFocus] ;
    [rootGlyph display: &bounds] ;
    [bGView unlockFocus] ;
  }

  return self ;
}  

- rightMouseDragged: (NXEvent *) anEvent ;
{ [NXApp printf: "R"] ;
  return self ;
}


- rightMouseUp: (NXEvent *) anEvent ;
{ [NXApp printf: "U"] ;
  return self ;
}

- windowDidResize: sender ;
{ NXRect aRect ;
  float x,y ; 
  [superview getBounds: &aRect] ;
  [self sizeTo: x = MAX(bounds.size.width,aRect.size.width)
              : y = MAX(bounds.size.height,aRect.size.height)] ;
  [[bGView window] sizeWindow:x :y] ; 
  [rootGlyph sizeTo: x :y] ;
  // NOTE: this generates 2 rectangles which will need to
  // be repaired in the bGView....
  // get current visible rectangle
  [[superview superview] getDocVisibleRect: &visibleRect] ;
  return self ;
}

- windowWillClose: sender ;
{ // our window is about to close; must free up 
  // stuff we created
  [[fGView window] free] ;
  [[bGView window] free] ;
  [[GlyphView glyphViewList] removeObject: self] ;
  return self ;
} 


@end